﻿using System;
using System.Collections.Generic;
using System.Timers;
using TSU.FAMC.ComputerEngineering.Classes.Circuits.Events;
using TSU.FAMC.ComputerEngineering.Classes.Enums;

namespace TSU.FAMC.ComputerEngineering.Classes.Circuits.Basis
{
    /// <summary>
    /// Base class for circuit's elements. Characterized by the time and a method of input signals processing.
    /// </summary>
    public class Element : Circuit
    {
        // Properties

        /// <summary>
        /// Element's delay timer.
        /// </summary>
        public Timer DelayTimer { get; set; }

        /// <summary>
        /// Resulting signal.
        /// </summary>
        public Signal ResultSignal { get; set; }

        /// <summary>
        /// Indicates whether delay time has been elapse.
        /// </summary>
        public bool DelayTimeElapsed { get; set; }

        /// <summary>
        /// Accepted signals.
        /// </summary>
        public List<Signal> InputSignals { get; set; }

        /// <summary>
        /// Last input signal.
        /// </summary>
        public Signal LastInputSignal { get; set; }

        /// <summary>
        /// Element delay in miliseconds.
        /// </summary>
        private int _delay;

        /// <summary>
        /// Delay property.
        /// </summary>
        public int Delay 
        {
            get
            {
                return _delay;
            }

            set
            {
                _delay = value;

                DelayTimeElapsed = Delay == 0;
            }
        }

        /// <summary>
        /// Unique identificator of the gate.
        /// </summary>
        public int Id { get; set; }

        /// <summary>
        /// Name of the element.
        /// </summary>
        public string Name { get; set; }


        // Methods

        /// <summary>
        /// Default contructor.
        /// </summary>
        public Element()
        {
            // registering event handlers
            RegisterEventHandlers();

            // registering element in the system
            Id = CircuitElementRegister.GenerateId();

            // setting defaults
            ResultSignal = new Signal {Value = BooleanValue.Undefined};
            LastInputSignal = new Signal {Value = BooleanValue.Undefined};
            InputSignals = new List<Signal>();

            // setting delay timer
            DelayTimer = new Timer();
            Delay = 0;

            DelayTimer.Elapsed += DelayTimerElapsed;
            DelayTimer.AutoReset = false;
        }

        /// <summary>
        /// Encapsulates event handler registration.
        /// </summary>
        private void RegisterEventHandlers()
        {
            var onInputSignalDelegate = new SignalEventHandler(OnInputSignalHandler);
            OnInputSignal += onInputSignalDelegate;            
        }

        /// <summary>
        /// Resets element state.
        /// </summary>
        override public void Reset()
        {
            InputSignals.Clear();
            ResultSignal = new Signal { Value = BooleanValue.Undefined };
            LastInputSignal = new Signal { Value = BooleanValue.Undefined };
            Delay = Delay;
        }

        /// <summary>
        /// Clones curcuit element.
        /// </summary>
        /// <returns>Cloned circuit element.</returns>
        public override object Clone()
        {
            return new Element
                       {
                           Delay = Delay, 
                           Id = Id, 
                           Name = Name
                       };
        }

        /// <summary>
        /// Processes inputs signals and produces a result signal.
        /// </summary>
        /// <returns>Resulting signal. If Value of the signal is BooleanValue.UNDEFINED it means processing failed.</returns>
        virtual public Signal Process()
        {
            if (InputSignals.Count == 0)
            {
                var result = new Signal {Value = BooleanValue.Undefined};
                return result;
            }

            // realization may vary depending of element type in child classes
            return InputSignals[0];
        }

        /// <summary>
        /// Transmittes signal to the output, according to the delays.
        /// </summary>
        public void TransmitSignal()
        {
            // checking whether delay time has been elapsed
            if (!DelayTimeElapsed || ResultSignal.Value == BooleanValue.Undefined)
            {
                return;
            }
            
            // transmitting signal by firing OnOutputSingal event

            var args = new SignalEventArgs {Signal = ResultSignal, Provider = this};

            // reseting state values before transmitting
            ResultSignal = new Signal { Value = BooleanValue.Undefined };
            LastInputSignal = new Signal { Value = BooleanValue.Undefined };
            Delay = Delay;

            FireOnOutputSignal(args);
        }


        // Event handlers

        /// <summary>
        /// OnOutputSignal event handler for input interface.
        /// </summary>
        /// <param name="sender">Sender object (input circuit).</param>
        /// <param name="e">Event arguments.</param>
        public void OnInputSignalHandler(object sender, SignalEventArgs e)
        {
            // if its a first input signal starting timer
            if (InputSignals.Count == 0 && Delay > 0)
            {
                // updating timer parameters
                DelayTimer.Interval = Delay;

                // starting timer
                DelayTimer.Start();
            }

            // memorizing input signal
            LastInputSignal = e.Signal;

            if (Inputs.Count == 0)
            {
                // it's a primary input of the circuit
                InputSignals.Clear();
                InputSignals.Add(e.Signal);
            }
            else
            {
                // signal has been received from another element
                var inputIdx = Inputs.IndexOf(e.Provider);
                if (inputIdx < 0)
                {
                    throw new Exception("Signal provider does not belong to the circuit input interface.");
                }
                if (inputIdx >= InputSignals.Count)
                {
                    // inserting fictive signals
                    for (var i = InputSignals.Count; i < inputIdx; i++)
                    {
                        InputSignals.Add(new Signal());
                    }
                    InputSignals.Add(e.Signal);
                }
                else
                {
                    // updating signal
                    InputSignals.Insert(inputIdx, e.Signal);
                    InputSignals.RemoveAt(inputIdx + 1);
                }
            }

            // processing signal
            ResultSignal = Process();

            // transmitting resulting signal
            TransmitSignal();
        }

        /// <summary>
        /// Invokes when delay interval elapsed.
        /// </summary>
        /// <param name="sender">Sender object.</param>
        /// <param name="e">Event arguments.</param>
        public void DelayTimerElapsed(object sender, ElapsedEventArgs e)
        {
            DelayTimer.Stop();
            DelayTimeElapsed = true;

            // transmitting signal
            TransmitSignal();
        }
    }
}
